#include "verilated_vcd_c.h"
#include "VysyxSoCFull___024root.h"
#include "VysyxSoCFull.h"
#include "VysyxSoCFull__Dpi.h"
#include "verilated.h"
#include "svdpi.h"
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <sys/time.h>

#define NVBOARD
#ifdef NVBOARD
#include "nvboard.h"
#endif // NVBOARD

#define VCD 1
#define CHISEL 1

#ifdef VCD
VerilatedContext* contextp = NULL;
VerilatedVcdC* tfp = NULL;
#endif

static VysyxSoCFull* top;

void nvboard_bind_all_pins(VysyxSoCFull* top);

// MROM: 0x2000_0000 ~ 0x2000_0fff

extern "C" {
  // from npcSim
  uint32_t paddr_read(uint32_t addr, int len);

  // to npcSim
  void step_and_dump_wave();
  int npc_main(int argc, char *argv[]);
  void init_sim();
  void step_one(unsigned int *inst, int *ebreak, unsigned int *dnpc, unsigned int *gpr, uint64_t *cycle, unsigned int pc);
  void exit_sim();
  void read_gpr(unsigned int *gpr);
}

extern "C" void flash_read(int32_t addr, int32_t *data) {
  // printf("falsh-addr: 0x%08x\n", addr);
  *data = paddr_read((addr + 0x30000000) & 0xfffffffc , 4);
  // printf("falsh-data: 0x%08x\n", *data);
}
extern "C" void mrom_read(int32_t addr, int32_t *data) {
  // *data = 0b00000000000100000000000001110011;
  *data = paddr_read(addr & 0xfffffffc, 4);
}

// psram: 0x8000_0000 ~ 0x9fff_ffff
uint8_t psram[0x20000000];
extern "C" void psram_read(int32_t addr, int32_t *data) {
  *data = ((unsigned int *)psram)[(addr & 0xfffffffc)/4];
  // printf("read, addr: 0x%08x data: 0x%08x\n", addr, *data);
}
extern "C" void psram_write(int32_t addr, int32_t data, int32_t size) {
  // printf("write, addr: 0x%08x data: 0x%08x, size: %d bytes\n", addr, data, size);
  switch (size) {
    case 1: psram[addr] = data & 0x000000ff; break;
    case 2: ((uint16_t*)psram)[addr/2] = data & 0x0000ffff; break;
    case 4: ((unsigned int *)psram)[(addr & 0xfffffffc)/4] = data; break;
  }
}

// sdram: 0xa000_0000~0xbfff_ffff
// chip 0
uint8_t sdram0_bank0[0x800000];
uint8_t sdram0_bank1[0x800000];
uint8_t sdram0_bank2[0x800000];
uint8_t sdram0_bank3[0x800000];

// chip 1
uint8_t sdram1_bank0[0x800000];
uint8_t sdram1_bank1[0x800000];
uint8_t sdram1_bank2[0x800000];
uint8_t sdram1_bank3[0x800000];

// chip 2
uint8_t sdram2_bank0[0x800000];
uint8_t sdram2_bank1[0x800000];
uint8_t sdram2_bank2[0x800000];
uint8_t sdram2_bank3[0x800000];

// chip 3
uint8_t sdram3_bank0[0x800000];
uint8_t sdram3_bank1[0x800000];
uint8_t sdram3_bank2[0x800000];
uint8_t sdram3_bank3[0x800000];

extern "C" void sdram_read(char chipnum, int32_t addr, int32_t *data) {
  int col  = (addr & 0x00001fff);
  int bank = (addr & 0x00006000) >> 13;
  int row  = (addr & 0x1fff8000) >> 15;
  // printf("chip: %d, row: %d, bank: %d, col: %d\n", chipnum, row, bank, col);

  if (chipnum == 0) {
    switch (bank) {
      case 0: *data = ((uint16_t *)sdram0_bank0)[row*512 + col]; break;
      case 1: *data = ((uint16_t *)sdram0_bank1)[row*512 + col]; break;
      case 2: *data = ((uint16_t *)sdram0_bank2)[row*512 + col]; break;
      case 3: *data = ((uint16_t *)sdram0_bank3)[row*512 + col]; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else if (chipnum == 1) {
    switch (bank) {
      case 0: *data = ((uint16_t *)sdram1_bank0)[row*512 + col]; break;
      case 1: *data = ((uint16_t *)sdram1_bank1)[row*512 + col]; break;
      case 2: *data = ((uint16_t *)sdram1_bank2)[row*512 + col]; break;
      case 3: *data = ((uint16_t *)sdram1_bank3)[row*512 + col]; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else if (chipnum == 2) {
    row = row & 0x00001fff;
    switch (bank) {
      case 0: *data = ((uint16_t *)sdram2_bank0)[row*512 + col]; break;
      case 1: *data = ((uint16_t *)sdram2_bank1)[row*512 + col]; break;
      case 2: *data = ((uint16_t *)sdram2_bank2)[row*512 + col]; break;
      case 3: *data = ((uint16_t *)sdram2_bank3)[row*512 + col]; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else if (chipnum == 3) {
    row = row & 0x00001fff;
    switch (bank) {
      case 0: *data = ((uint16_t *)sdram3_bank0)[row*512 + col]; break;
      case 1: *data = ((uint16_t *)sdram3_bank1)[row*512 + col]; break;
      case 2: *data = ((uint16_t *)sdram3_bank2)[row*512 + col]; break;
      case 3: *data = ((uint16_t *)sdram3_bank3)[row*512 + col]; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else {
    printf("No sdram chip%d\n", chipnum);
  }
}
extern "C" void sdram_write(char chipnum, int32_t addr, int32_t data, int32_t mask) {
  int wdata = 0;
  sdram_read(chipnum, addr, &wdata);

  int col  = (addr & 0x00001fff);
  int bank = (addr & 0x00006000) >> 13;
  int row  = (addr & 0x1fff8000) >> 15;
  int wmask = (~mask) & 0x00000003;
  // printf("chip: %d, row: %d, bank: %d, col: %d, wmask: %x, data: %x\n", chipnum, row, bank, col, wmask, data);

  switch (wmask) {
    case 1:  wdata = (wdata & ~0x000000ff) + (data & 0x000000ff); break;
    case 2:  wdata = (wdata & ~0x0000ff00) + (data & 0x0000ff00); break;
    case 3:  wdata = (wdata & ~0x0000ffff) + (data & 0x0000ffff); break;
  }

  if (chipnum == 0) {
    switch (bank) {
      case 0: ((uint16_t *)sdram0_bank0)[row*512 + col] = wdata & 0x0000ffff; break;
      case 1: ((uint16_t *)sdram0_bank1)[row*512 + col] = wdata & 0x0000ffff; break;
      case 2: ((uint16_t *)sdram0_bank2)[row*512 + col] = wdata & 0x0000ffff; break;
      case 3: ((uint16_t *)sdram0_bank3)[row*512 + col] = wdata & 0x0000ffff; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else if (chipnum == 1) {
    switch (bank) {
      case 0: ((uint16_t *)sdram1_bank0)[row*512 + col] = wdata & 0x0000ffff; break;
      case 1: ((uint16_t *)sdram1_bank1)[row*512 + col] = wdata & 0x0000ffff; break;
      case 2: ((uint16_t *)sdram1_bank2)[row*512 + col] = wdata & 0x0000ffff; break;
      case 3: ((uint16_t *)sdram1_bank3)[row*512 + col] = wdata & 0x0000ffff; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else if (chipnum == 2) {
    row = row & 0x00001fff;
    switch (bank) {
      case 0: ((uint16_t *)sdram2_bank0)[row*512 + col] = wdata & 0x0000ffff; break;
      case 1: ((uint16_t *)sdram2_bank1)[row*512 + col] = wdata & 0x0000ffff; break;
      case 2: ((uint16_t *)sdram2_bank2)[row*512 + col] = wdata & 0x0000ffff; break;
      case 3: ((uint16_t *)sdram2_bank3)[row*512 + col] = wdata & 0x0000ffff; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else if (chipnum == 3) {
    row = row & 0x00001fff;
    switch (bank) {
      case 0: ((uint16_t *)sdram3_bank0)[row*512 + col] = wdata & 0x0000ffff; break;
      case 1: ((uint16_t *)sdram3_bank1)[row*512 + col] = wdata & 0x0000ffff; break;
      case 2: ((uint16_t *)sdram3_bank2)[row*512 + col] = wdata & 0x0000ffff; break;
      case 3: ((uint16_t *)sdram3_bank3)[row*512 + col] = wdata & 0x0000ffff; break;
      default: printf("No bank %d\n", bank);
    }
  }
  else {
    printf("No sdram chip%d\n", chipnum);
  }
}

void step_and_dump_wave() {
  top->eval();
#ifdef VCD
  contextp->timeInc(1);
  tfp->dump(contextp->time());
#endif
}

void single_cycle() {
  top->clock=0; step_and_dump_wave();
  top->clock=1; step_and_dump_wave();
#ifdef NVBOARD
  nvboard_update();
#endif // NVBOARD
}

void init_sim() {
#ifdef VCD
  contextp = new VerilatedContext;
  tfp = new VerilatedVcdC;
#endif
  top = new VysyxSoCFull;
#ifdef VCD
  contextp->traceEverOn(true);
  top->trace(tfp, 0);
  tfp->open("dump.vcd");
#endif

#ifdef NVBOARD
  nvboard_bind_all_pins(top);
  nvboard_init();
  nvboard_update();
#endif // NVBOARD

  top->reset=1; single_cycle();
  for (int i = 0; i < 10; i++) single_cycle();
  top->reset=0; single_cycle();
}

void exit_sim() {
  step_and_dump_wave();
  // tfp->close();
  top->final();
}

void step_one(unsigned int *inst, int *ebreak, unsigned int *dnpc, unsigned int *gpr, uint64_t *cycle, unsigned int pc) {

  const svScope scope = svGetScopeFromName("TOP.ysyxSoCFull.asic.cpu.cpu.idu.decoder.ebreak");
  assert(scope);
  svSetScope(scope);

  char done = 0;

  do {
    single_cycle(); (*cycle) ++;

    char wb_valid  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__wbu__DOT__valid;
    unsigned int wb_pc = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__wbu__DOT__wbu_pc;
    unsigned int wb_inst = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__wbu__DOT__wbu_inst;
    unsigned int wb_pc_sel = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__wbu__DOT__wbu_PC_sel;
    unsigned int wb_br_taken = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__wbu__DOT__wbu_br_taken;
    unsigned int wb_Alu_out = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__wbu__DOT__wbu_Alu_out;

    *inst = wb_inst;
    *dnpc = wb_br_taken == 1 ? wb_Alu_out : wb_pc + 4;
    // char inst_valid = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__ifu__DOT___inst_valid;

    // done = inst_valid & wb_valid;
    done = wb_valid;

    // when done is valid, excute another cycle
    if (done == 1 && wb_pc == pc) { single_cycle(); (*cycle) ++; }

  } while (done == 0);

  // printf("pc->%#08x\n", pc);
  // *inst = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__ifu__DOT___inst;
  // *dnpc = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__ifu__DOT__pc__DOT__pc;
  if (pc <= 0xa0000c34 && pc >= 0x30000000)
    printf("pc: %#08x   inst: %#08x  dnpc:%#08x\n", pc, *inst, *dnpc);
  read_gpr(gpr);

  // IsEbreak(ebreak);
  *ebreak = *inst == 0b00000000000100000000000001110011 ? 1 : 0;
}

void read_gpr(unsigned int *gpr) {
    gpr[0]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_0;
    gpr[1]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_1;
    gpr[2]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_2;
    gpr[3]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_3;
    gpr[4]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_4;
    gpr[5]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_5;
    gpr[6]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_6;
    gpr[7]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_7;
    gpr[8]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_8;
    gpr[9]  = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_9;
    gpr[10] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_10;
    gpr[11] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_11;
    gpr[12] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_12;
    gpr[13] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_13;
    gpr[14] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_14;
    gpr[15] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_15;
    gpr[16] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_16;
    gpr[17] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_17;
    gpr[18] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_18;
    gpr[19] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_19;
    gpr[20] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_20;
    gpr[21] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_21;
    gpr[22] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_22;
    gpr[23] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_23;
    gpr[24] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_24;
    gpr[25] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_25;
    gpr[26] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_26;
    gpr[27] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_27;
    gpr[28] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_28;
    gpr[29] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_29;
    gpr[30] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_30;
    gpr[31] = top->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__idu__DOT__rf__DOT__rf_31;
}

int main(int argc, char** argv) {
  Verilated::commandArgs(argc, argv);

#ifndef CHISEL
    printf("Please run npc of chisel version!!!\n");
    assert(0)
#endif // !CHISEL

  npc_main(argc, argv);

#ifdef NVBOARD
  nvboard_quit();
#endif // NVBOARD

  return 0;
}
